/*!	 tool/main.cpp
**	 SYNFIG Tool
**
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2007, 2008 Chris Moore
**	Copyright (c) 2009-2015 Diego Barrios Romero
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include <iostream>
#include <string>
#include <list>

#include <boost/program_options/options_description.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/variables_map.hpp>
#include <boost/tokenizer.hpp>
#include <boost/token_functions.hpp>
#include <boost/filesystem.hpp>
#include <boost/format.hpp>

#include <glibmm.h>

#include <synfig/localization.h>
#include <synfig/general.h>

#include <synfig/module.h>
#include <synfig/importer.h>
#include <synfig/cairoimporter.h>
#include <synfig/layer.h>
#include <synfig/canvas.h>
#include <synfig/target.h>
#include <synfig/targetparam.h>
#include <synfig/string.h>
#include <synfig/paramdesc.h>
#include <synfig/main.h>
#include <autorevision.h>
#include "definitions.h"
#include "progress.h"
#include "job.h"
#include "synfigtoolexception.h"
#include "optionsprocessor.h"
#include "joblistprocessor.h"
#include "printing_functions.h"

#include "named_type.h"
#endif

namespace po = boost::program_options;
namespace bfs = boost::filesystem;

std::string _appendAlphaToFilename(std::string input_filename)
{
    bfs::path filename(input_filename);
    bfs::path alpha_filename(filename.stem().string() + "-alpha" +
                             filename.extension().string());
    return bfs::path(filename.parent_path() / alpha_filename).string();
}

int main(int argc, char* argv[])
{
    setlocale(LC_ALL, "");

    SynfigToolGeneralOptions::create_singleton_instance(argv[0]);

    bfs::path binary_path =
        SynfigToolGeneralOptions::instance()->get_binary_path();

#ifdef ENABLE_NLS
    boost::filesystem::path locale_path =
        binary_path.parent_path().parent_path();
    locale_path = locale_path / "share" / "locale";
    bindtextdomain("synfig", Glib::locale_from_utf8(locale_path.string()).c_str());
    bind_textdomain_codeset("synfig", "UTF-8");
    textdomain("synfig");
#endif

    if (!SYNFIG_CHECK_VERSION()) {
        std::cerr << _("FATAL: Synfig Version Mismatch") << std::endl;
        return SYNFIGTOOL_BADVERSION;
    }

    try {
        if (argc == 1) {
            throw (SynfigToolException(SYNFIGTOOL_MISSINGARGUMENT));
        }

        named_type<std::string>* target_arg_desc = new named_type<std::string>("module");
        named_type<int>* width_arg_desc = new named_type<int>("NUM");
        named_type<int>* height_arg_desc = new named_type<int>("NUM");
        named_type<int>* span_arg_desc = new named_type<int>("NUM");
        named_type<int>* antialias_arg_desc = new named_type<int>("1..30");
        named_type<int>* quality_arg_desc = new named_type<int>("0..10");
        named_type<float>* gamma_arg_desc = new named_type<float>("NUM (=2.2)");
        named_type<int>* threads_arg_desc = new named_type<int>("NUM");
        named_type<int>* verbosity_arg_desc = new named_type<int>("NUM");
        named_type<std::string>* canvas_arg_desc = new named_type<std::string>("canvas-id");
        named_type<std::string>* output_file_arg_desc = new named_type<std::string>("filename");
        named_type<std::string>* input_file_arg_desc = new named_type<std::string>("filename");
        named_type<float>* fps_arg_desc = new named_type<float>("NUM");
        named_type<std::string>* time_arg_desc = new named_type<std::string>("seconds");
        named_type<std::string>* begin_time_arg_desc = new named_type<std::string>("seconds");
        named_type<std::string>* start_time_arg_desc = new named_type<std::string>("seconds");
        named_type<std::string>* end_time_arg_desc = new named_type<std::string>("seconds");
        named_type<int>* dpi_arg_desc = new named_type<int>("NUM");
        named_type<int>* dpi_x_arg_desc = new named_type<int>("NUM");
        named_type<int>* dpi_y_arg_desc = new named_type<int>("NUM");
        named_type<std::string>* append_filename_arg_desc = new named_type<std::string>("filename");
        named_type<std::string>* sequence_separator_arg_desc = new named_type<std::string>("string");
        named_type<std::string>* canvas_info_fields_arg_desc = new named_type<std::string>("fields");
        named_type<std::string>* layer_info_field_arg_desc = new named_type<std::string>("layer-name");
        named_type<std::string>* video_codec_arg_desc = new named_type<std::string>("codec");
        named_type<int>* video_bitrate_arg_desc = new named_type<int>("bitrate");

        po::options_description po_settings(_("Settings"));
        po_settings.add_options()
        ("target,t", target_arg_desc, _("Specify output target (Default: PNG)"))
        ("width,w", width_arg_desc, _("Set the image width in pixels (Use zero for file default)"))
        ("height,h", height_arg_desc, _("Set the image height in pixels (Use zero for file default)"))
        ("span,s", span_arg_desc, _("Set the diagonal size of image window (Span)"))
        ("antialias,a", antialias_arg_desc, _("Set antialias amount for parametric renderer."))
        ("quality,Q", quality_arg_desc->default_value(DEFAULT_QUALITY), (boost::format(_("Specify image quality for accelerated renderer (Default: %d)")) % DEFAULT_QUALITY).str().c_str())
        ("gamma,g", gamma_arg_desc, _("Gamma"))
        ("threads,T", threads_arg_desc, _("Enable multithreaded renderer using the specified number of threads"))
        ("input-file,i", input_file_arg_desc, _("Specify input filename"))
        ("output-file,o", output_file_arg_desc, _("Specify output filename"))
        ("sequence-separator", sequence_separator_arg_desc, _("Output file sequence separator string (Use double quotes if you want to use spaces)"))
        ("canvas,c", canvas_arg_desc, _("Render the canvas with the given id instead of the root."))
        ("fps", fps_arg_desc, _("Set the frame rate"))
        ("time", time_arg_desc, _("Render a single frame at <seconds>"))
        ("begin-time", begin_time_arg_desc, _("Set the starting time"))
        ("start-time", start_time_arg_desc, _("Set the starting time"))
        ("end-time", end_time_arg_desc, _("Set the ending time"))
        ("dpi", dpi_arg_desc, _("Set the physical resolution (Dots-per-inch)"))
        ("dpi-x", dpi_x_arg_desc, _("Set the physical X resolution (Dots-per-inch)"))
        ("dpi-y", dpi_y_arg_desc, _("Set the physical Y resolution (Dots-per-inch)"))
        ;

        po::options_description po_switchopts(_("Switch options"));
        po_switchopts.add_options()
        ("verbose,v", verbosity_arg_desc, _("Output verbosity level"))
        ("quiet,q", _("Quiet mode (No progress/time-remaining display)"))
        ("benchmarks,b", _("Print benchmarks"))
        ("extract-alpha,x", _("Extract alpha"))
        ;

        po::options_description po_misc(_("Misc options"));
        po_misc.add_options()
        ("append", append_filename_arg_desc, _("Append layers in <filename> to composition"))
        ("canvas-info", canvas_info_fields_arg_desc, _("Print out specified details of the root canvas"))
        ("canvases", _("Print out the list of exported canvases in the composition"))
        ;

        po::options_description po_ffmpeg(_("FFMPEG target options"));
        po_ffmpeg.add_options()
        ("video-codec", video_codec_arg_desc, _("Set the codec for the video. See --ffmpeg-video-codecs"))
        ("video-bitrate", video_bitrate_arg_desc, _("Set the bitrate for the output video"))
        ;

        po::options_description po_info(_("Synfig info options"));
        po_info.add_options()
        ("help", _("Produce this help message"))
        ("importers", _("Print out the list of available importers"))
        ("info", _("Print out misc build information"))
        ("layers", _("Print out the list of available layers"))
        ("layer-info", layer_info_field_arg_desc, _("Print out layer's description, parameter info, etc."))
        ("license", _("Print out license information"))
        ("modules", _("Print out the list of loaded modules"))
        ("targets", _("Print out the list of available targets"))
        ("target-video-codecs", _("Print out the list of available video codecs when encoding through FFMPEG"))
        ("valuenodes", _("Print out the list of available ValueNodes"))
        ("version", _("Print out version information"))
        ;

        po::options_description po_hidden("");
        po_hidden.add_options()
        ("list-canvases", _("Print out the list of exported canvases in the composition"))
        ;

#ifdef _DEBUG
        po::options_description po_debug(_("Synfig debug flags"));
        po_debug.add_options()
        ("guid-test", _("Test GUID generation"))
        ("signal-test", _("Test signal implementation"))
        ;
#endif

        // The first positional option will be treated as the input file
        po::positional_options_description po_positional;
        po_positional.add("input-file", 1);

        // Declare an options description instance which will include
        // all the options
        po::options_description po_all("");
        po_all.add(po_settings).add(po_switchopts).add(po_misc).add(po_info).add(po_ffmpeg).add(po_hidden);

#ifdef _DEBUG
        po_all.add(po_debug);
#endif

        // Declare an options description instance which will be shown
        // to the user
        po::options_description po_visible("");
        po_visible.add(po_settings).add(po_switchopts).add(po_misc).add(po_ffmpeg);

#ifdef _DEBUG
        po_visible.add(po_debug);
#endif

        po_visible.add(po_info);

        po::variables_map vm;

        try {
            po::store(po::command_line_parser(argc, argv).options(po_all).
                      positional(po_positional).run(), vm);
        } catch (std::exception &e) {
            std::cout << std::endl << e.what() << std::endl;
            std::cout << _("Try 'synfig --help' for more information") << std::endl;
            return SYNFIGTOOL_UNKNOWNARGUMENT;
        }

        OptionsProcessor op(vm, po_visible);

        op.process_settings_options();

#ifdef _DEBUG
        op.process_debug_options();
#endif

        // TODO: Optional load of main only if needed. i.e. not needed to display help
        // Synfig Main initialization needs to be after verbose and
        // before any other where it's used
        Progress p(binary_path.string().c_str());
        synfig::Main synfig_main(binary_path.parent_path().string(), &p);

        op.process_info_options();

        std::list<Job> job_list;

        Job job;
        job = op.extract_job();
        job.desc = job.canvas->rend_desc() = op.extract_renddesc(job.canvas->rend_desc());

        if (job.extract_alpha) {
            job.alpha_mode = synfig::TARGET_ALPHA_MODE_REDUCE;
            job_list.push_front(job);
            job.alpha_mode = synfig::TARGET_ALPHA_MODE_EXTRACT;
            job.outfilename = _appendAlphaToFilename(job.outfilename);
            job_list.push_front(job);
        } else {
            job_list.push_front(job);
        }

        process_job_list(job_list, op.extract_targetparam());

        return SYNFIGTOOL_OK;

    } catch (SynfigToolException& e) {
        exit_code code = e.get_exit_code();

        if (code != SYNFIGTOOL_HELP && code != SYNFIGTOOL_OK) {
            std::cerr << e.get_message().c_str() << std::endl;
        }

        if (code == SYNFIGTOOL_MISSINGARGUMENT) {
            print_usage();
        }

        return code;
    } catch (std::exception& e) {
        std::cout << e.what() << std::endl;
    }
}